/*
 * Copyright ?2006, Ashley van Gerven - ashley._v_g_@gmail.com (NB: remove '_' chars)
 * All rights reserved.
 *
 * Use of this script, with or without modification, is permitted
 * provided that the above copyright notice and disclaimer below is not removed.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */


//#define CLR_v1  // << N.B. Comment this line if compiling with .NET 2.0 or later


using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Text.RegularExpressions;
using System.Drawing;


#if !CLR_v1
[assembly: WebResource("Avg.Controls.SmartPager.js", "text/javascript")]
#endif


namespace Avg.Controls
{
	/// <summary>
	/// Flickr-style pager control with a go-to-page feature
	/// </summary>
	[ToolboxData("<{0}:SmartPager runat=\"server\" />")]
	[ToolboxBitmap(typeof(SmartPager), "SmartPagerIcon.bmp")]
	public class SmartPager : Control, ICloneable, IPostBackDataHandler
	{
		#region Public Properties
		/// <summary>
		/// Gets or sets the number of page numbers to display in the list
		/// </summary>
		[Category("Appearance"), DefaultValue(9), Description("Number of page numbers to display in the list")]
		public int Display
		{
			get { return display; }
			set { display = value; }
		}

		/// <summary>
		/// Gets or sets the number of pages available in the data source
		/// </summary>
		[Category("Misc"), Description("Number of pages available in the data source")]
		public int PageCount
		{
			get { return pageCount; }
			set { pageCount = value; }
		}

		/// <summary>
		/// Gets or sets the current page number
		/// </summary>
		[Browsable(false)]
		public int CurrentPage
		{
			get { return currentPage; }
			set
			{
				currentPage = value;
				ViewState[ClientID + "_CurrentPage"] = currentPage;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to output the Next & Previous links
		/// </summary>
		[Category("Appearance"), Description("Number of pages available in the data source")]
		public bool OutputNextPrevLinks
		{
			get { return outputNextPrevLinks; }
			set { outputNextPrevLinks = value; }
		}

		/// <summary>
		/// Gets or sets the style for non-clickable Next & Previous links
		/// </summary>
		[Category("Appearance"), Description("Number of pages available in the data source")]
		public string DisabledNextPrevStyle
		{
			get { return disabledNextPrevStyle; }
			set { disabledNextPrevStyle = value; }
		}

		/// <summary>
		/// Gets or sets the text for the 'Previous' link
		/// </summary>
		[Category("Appearance"), Description("Number of pages available in the data source")]
		public string NavigatePreviousText
		{
			get { return navigatePreviousText; }
			set { navigatePreviousText = value; }
		}

		/// <summary>
		/// Gets or sets the text for the 'Next' link
		/// </summary>
		[Category("Appearance"), Description("Number of pages available in the data source")]
		public string NavigateNextText
		{
			get { return navigateNextText; }
			set { navigateNextText = value; }
		}

		/// <summary>
		/// Gets or sets the style attribute for the main table
		/// </summary>
		[Category("Appearance"), DefaultValue(""), Description("Number of pages available in the data source")]
		public string MainTableStyle
		{
			get { return mainTableStyle; }
			set { mainTableStyle = value; }
		}

		/// <summary>
		/// Gets or sets the location of SmartPager.js
		/// </summary>
		[Category("Misc"), DefaultValue(null), Description("Location of SmartPager.js")]
		public string ScriptPath
		{
			get { return scriptPath; }
			set { scriptPath = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether to output the first and last page links
		/// </summary>
		[Category("Appearance"), Description("Determines whether to output the first and last page links")]
		public bool OutputFirstAndLastLinks
		{
			get { return outputFirstAndLastLinks; }
			set { outputFirstAndLastLinks = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether clicking the ellipses should display the Go-to-page layer
		/// </summary>
		[Category("Behavior"), Description("Determines whether clicking the ellipses should display the Go-to-page layer")]
		public bool EnableGoToPage
		{
			get { return enableGoToPage; }
			set { enableGoToPage = value; }
		}

		/// <summary>
		/// Gets or sets the font size for page links
		/// </summary>
		[Category("Appearance")]
		public string FontSize
		{
			get { return fontSize; }
			set { fontSize = value; }
		}

		/// <summary>
		/// Gets or sets the color for page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkForeColor
		{
			get { return pageLinkForeColor; }
			set { pageLinkForeColor = value; }
		}

		/// <summary>
		/// Gets or sets the background color for page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkBackColor
		{
			get { return pageLinkBackColor; }
			set { pageLinkBackColor = value; }
		}

		/// <summary>
		/// Gets or sets the hover color for page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkHoverForeColor
		{
			get { return pageLinkHoverForeColor; }
			set { pageLinkHoverForeColor = value; }
		}

		/// <summary>
		/// Gets or sets the hover background color for page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkHoverBackColor
		{
			get { return pageLinkHoverBackColor; }
			set { pageLinkHoverBackColor = value; }
		}

		/// <summary>
		/// Gets or sets the color for selected page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkSelectedForeColor
		{
			get { return pageLinkSelectedForeColor; }
			set { pageLinkSelectedForeColor = value; }
		}

		/// <summary>
		/// Gets or sets the background color for selected page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkSelectedBackColor
		{
			get { return pageLinkSelectedBackColor; }
			set { pageLinkSelectedBackColor = value; }
		}

		/// <summary>
		/// Gets or sets the amount of padding in pixels of page number boxes
		/// </summary>
		[Category("Appearance")]
		public int PageNumberBoxPadding
		{
			get { return pageNumberBoxPadding; }
			set { pageNumberBoxPadding = value; }
		}

		/// <summary>
		/// Gets or sets the border width in pixels of page number boxes
		/// </summary>
		[Category("Appearance")]
		public int PageNumberBoxBorderWidth
		{
			get { return pageNumberBoxBorderWidth; }
			set { pageNumberBoxBorderWidth = value; }
		}

		/// <summary>
		/// Gets or sets the border color of page number boxes
		/// </summary>
		[Category("Appearance")]
		public string PageNumberBoxBorderColor
		{
			get { return pageNumberBoxBorderColor; }
			set { pageNumberBoxBorderColor = value; }
		}

		/// <summary>
		/// Gets or sets the text to indicate page numbers that are omitted
		/// </summary>
		[Category("Appearance")]
		public string EllipsisText
		{
			get { return ellipsisText; }
			set { ellipsisText = value; }
		}

		/// <summary>
		/// Gets or sets the name of the JavaScript function to handle the page-change (overriding the default Postback behavior)
		/// </summary>
		[Category("Behavior"), Description("Specify a JS function to handle the page-change (overriding the default Postback behavior)")]
		public string ClientPageChanged
		{
			get { return clientPageChanged; }
			set { clientPageChanged = value; }
		}

		/// <summary>
		/// Gets or sets the text for the textbox-label on the Go-to-page layer
		/// </summary>
		[Category("Appearance")]
		public string PageLabelText
		{
			get { return pageLabelText; }
			set { pageLabelText = value; }
		}

		/// <summary>
		/// Gets or sets the text for the GO button on the Go-to-page layer
		/// </summary>
		[Category("Appearance")]
		public string GoButtonText
		{
			get { return goButtonText; }
			set { goButtonText = value; }
		}
		#endregion


		#region Public Events
		/// <summary>
		/// Occurs when the user navigates to a page on this control
		/// </summary>
		public event EventHandler PageChanged;
		#endregion


		#region Private Members
		private int display = 9;
		private int pageCount = 20;
		private int currentPage = 1;
		private bool outputNextPrevLinks = true;
		private string fontSize = null;
		private bool enableGoToPage = true;
		private bool outputFirstAndLastLinks = true;
		private string scriptPath = "";
		private string mainTableStyle = "";
		private string navigateNextText = "Next &#187;";
		private string navigatePreviousText = "&#171; Previous";
		private string disabledNextPrevStyle = "display:none";
		private string pageLinkForeColor = null;
		private string pageLinkBackColor = null;
		private string pageLinkHoverForeColor = null;
		private string pageLinkHoverBackColor = null;
		private string pageLinkSelectedForeColor = "red";
		private string pageLinkSelectedBackColor = null;
		private int pageNumberBoxPadding = 5;
		private int pageNumberBoxBorderWidth = 1;
		private string pageNumberBoxBorderColor = "#ccc";
		private string ellipsisText = " &#133; ";
		private string clientPageChanged = null;
		private string pageLabelText = "Page:";
		private string goButtonText = "GO";

		private string[] tooltips = null;
		#endregion



		protected override void OnInit(EventArgs e)
		{
			this.Load += new EventHandler(SmartPager_Load);
			base.OnInit(e);
		}


		private void SmartPager_Load(object sender, EventArgs e)
		{
#if CLR_v1
            Page.RegisterClientScriptBlock("SmartPager_js", "<script language=JavaScript src=\"" + scriptPath + "SmartPager.js\"></script>");
			Page.RegisterClientScriptBlock("SmartPager_js2", string.Format("<script language=JavaScript>document.getElementById('smartPagerPageLabel').innerHTML='{0}'; document.getElementById('smartPagerGoLabel').innerHTML='{1}'</script>", pageLabelText, goButtonText));
#else
			bool foundRsrc = false;
			foreach (string rsrcName in this.GetType().Assembly.GetManifestResourceNames())
			{
				if (rsrcName.EndsWith(".SmartPager.js"))
				{
					foundRsrc = true;
					break;
				}
			}
			string scriptUrl = (foundRsrc) ? Page.ClientScript.GetWebResourceUrl(this.GetType(), "Avg.Controls.SmartPager.js") : scriptPath + "SmartPager.js";
			Page.ClientScript.RegisterClientScriptInclude("SmartPager_js", scriptUrl);
			
			Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "SmartPager_js2", string.Format("<script language=JavaScript>document.getElementById('smartPagerPageLabel').innerHTML='{0}'; document.getElementById('smartPagerGoLabel').innerHTML='{1}'</script>", pageLabelText, goButtonText));
#endif
		}


		protected override void Render(HtmlTextWriter writer)
		{
			if (PageCount <= 1)
				Display = -1;

			int[] links;

			if (Display == -1)
			{
				links = new int[PageCount];
				for (int i = 0; i < links.Length; i++)
					links[i] = i + 1;
			}
			else
			{
				links = new int[Display];

				// current page in the middle of our range
				int middle = (int)Math.Ceiling(Display / 2.0) - 1;
				links[middle] = currentPage;

				// pages preceding current page
				for (int i = middle - 1; i >= 0; i--)
					links[i] = links[i + 1] - 1;

				// pages following current page
				for (int i = middle + 1; i < links.Length; i++)
					links[i] = links[i - 1] + 1;


				// Get rid of page numbers exceeding PageCount ("shift" page numbers to the right)
				while (links[links.Length - 1] > PageCount)
				{
					for (int i = 0; i < links.Length; i++)
						links[i]--;
				}

				// Get rid of 0 or negative pages ("shift" page numbers to the left)
				while (links[0] <= 0)
				{
					for (int i = 0; i < links.Length; i++)
						links[i]++;
				}

				// assign -1 to pages over PageCount
				for (int i = links.Length - 1; i >= 0; i--)
				{
					if (links[i] > PageCount)
						links[i] = -1;
					else
						break;
				}
			}


			// javascript tooltips array
			StringBuilder tooltipsJsArr = new StringBuilder();
			if (tooltips != null)
			{
				foreach (string s in tooltips)
					tooltipsJsArr.AppendFormat("\"{0}\",", Page.Server.HtmlEncode(s));
				if (tooltipsJsArr.Length > 0)
					tooltipsJsArr.Remove(tooltipsJsArr.Length - 1, 1);
			}


			// ** HTML output begins **

			// JS variables for go-to-page popup
			writer.WriteLine("<script>var {0}$pagerPageCount = {1}; var {0}$tooltipsArr = [{2}]; var {0}$callJS = '{3}'</script>", ClientID, PageCount, tooltipsJsArr, clientPageChanged);


			// CSS
			string userAgent = "MSIE";
			try 
			{
				userAgent = Page.Request.UserAgent; // VS designer doesn't have a Request
			}
			catch { }
			string MsIEOnly = (userAgent != null && userAgent.IndexOf("MSIE") != -1) ? "background-color:{2};" : "";

			writer.WriteLine("<style type=text/css>");
			writer.WriteLine("a.pagerLink_{0} {{ color:{1}; text-decoration:none; font-size:{3}; }}", this.ClientID, pageLinkForeColor, pageLinkBackColor, fontSize);  // background-color:{2}; 
			writer.WriteLine("a.pagerLink_{0} span {{ background-color:{1}; }}", this.ClientID, pageLinkBackColor);
			writer.WriteLine("a.pagerLink_{0}:hover {{ color:{1}; " + MsIEOnly + "}}", this.ClientID, pageLinkHoverForeColor, pageLinkHoverBackColor);
			writer.WriteLine("a.pagerLink_{0}:hover span {{ background-color:{1}; }}", this.ClientID, pageLinkSelectedBackColor); // This has no effect in IE
			writer.WriteLine("a.pagerLinkSel_{0} {{ color:{1}; text-decoration:none; font-weight:bold; font-size:{3}; }}", this.ClientID, pageLinkSelectedForeColor, pageLinkSelectedBackColor, fontSize); // background-color:{2}; 
			writer.WriteLine("a.pagerLinkSel_{0} span {{ background-color:{1}; }}", this.ClientID, pageLinkSelectedBackColor);
			writer.WriteLine("span.pagerPageNo_{0} {{ height:1; cursor:hand; padding:{1}px; margin-left:2px; margin-right:2px; border:{2}px solid {3}; }}", this.ClientID, pageNumberBoxPadding, pageNumberBoxBorderWidth, pageNumberBoxBorderColor);
			writer.WriteLine("</style>");


			// main table
			writer.WriteLine("<table border=0 cellpadding=0 cellspacing=0 style=\"" + mainTableStyle + "\"><tr>");


			if (outputNextPrevLinks)
			{
				// previous link
				writer.Write("<td nowrap><nobr>");
				if (currentPage > 1)
					writer.Write("<a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class=pagerLink_{0} title=\"{2}\">&#171; Previous</a> &nbsp;", ClientID, currentPage - 1, getTooltipText(currentPage - 2));
				else
					writer.Write("<div style=\"{0}\">{1} &nbsp;</div>", disabledNextPrevStyle, navigatePreviousText);
				writer.WriteLine("</nobr></td>");
			}


			// page numbers
			writer.Write("<td align=center style='padding:3 0 3 0'>");


			if (links[0] != 1)
			{
				if (outputFirstAndLastLinks)
					// output link to first page number
					writer.Write("<a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class={2}_{0} title=\"{3}\"><span class=pagerPageNo_{0}>{1}</span></a>", ClientID, 1, (1 == currentPage) ? "pagerLinkSel" : "pagerLink", getTooltipText(0));

				if (links[0] != 2)
				{
					// output ellipsis
					if (enableGoToPage)
						writer.Write("<a href=# onclick=\"SmartPagerID='{0}';showPager(this);return false\" class=pagerLink_{0} style='position:relative;background-color:transparent;'>{1}</a>", ClientID, ellipsisText);
					else
						writer.Write(ellipsisText);
				}
			}


			// output page number links
			for (int i = 0; i < links.Length; i++)
			{
				if (links[i] == -1)
					break;

				writer.WriteLine("<a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class={2}_{0} title=\"{3}\"><span class=pagerPageNo_{0}>{1}</span></a", ClientID, links[i], (links[i] == currentPage) ? "pagerLinkSel" : "pagerLink", getTooltipText(links[i] - 1));
				writer.Write(">"); // span the </a> tag accross two lines so no line break between tags in HTML

				if (Display == -1)
					writer.Write(" "); // write a space so page numbers can wrap if displaying ALL page links
			}


			if (links[links.Length - 1] != -1 && links[links.Length - 1] != PageCount)
			{
				if (links[links.Length - 1] != PageCount - 1)
				{
					// output ellipsis
					if (enableGoToPage)
						writer.Write("<a href=#o onclick=\"SmartPagerID='{0}';showPager(this);return false\" class=pagerLink_{0} style='position:relative;background-color:transparent;'>{1}</a>", ClientID, ellipsisText);
					else
						writer.Write(ellipsisText);
				}

				if (outputFirstAndLastLinks)
					// output link to last page number
					writer.Write("<a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class={2}_{0} title=\"{3}\"><span class=pagerPageNo_{0}>{1}</span></a>", ClientID, PageCount, (PageCount == currentPage) ? "pagerLinkSel" : "pagerLink", getTooltipText(PageCount - 1));
			}
			writer.WriteLine("</div></td>");


			if (outputNextPrevLinks)
			{
				// next link
				writer.Write("<td nowrap align=right><nobr>");
				if (currentPage < PageCount)
					writer.Write("&nbsp; <a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class=pagerLink_{0} title=\"{2}\">Next &#187;</a>", ClientID, currentPage + 1, getTooltipText(currentPage));
				else
					writer.Write("<div style=\"{0}\">&nbsp; {1}</div>", disabledNextPrevStyle, navigateNextText);
				writer.WriteLine("</nobr></td>");
			}

			writer.WriteLine("</tr></table>");

			writer.WriteLine("<input type=hidden name='{0}' id='hdn{1}'>", UniqueID, ClientID);
		}


		// Check postback data (IPostBackDataHandler required method)
		public virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)
		{
			// get the previously selected page from the viewstate
			if (ViewState[ClientID + "_CurrentPage"] != null)
				currentPage = Convert.ToInt32(ViewState[ClientID + "_CurrentPage"]);

			bool changed = (postCollection[postDataKey] != "");
			if (changed)
				CurrentPage = Convert.ToInt32(postCollection[postDataKey]);

			return changed;
		}


		// Raise the PageChanged event (IPostBackDataHandler required method)
		public virtual void RaisePostDataChangedEvent()
		{
			OnPageChanged(EventArgs.Empty);
		}


		protected virtual void OnPageChanged(EventArgs e)
		{
			if (PageChanged != null)
				PageChanged(this, e);
		}


		/// <summary>
		/// Specify the tooltips for the page numbers
		/// </summary>
		/// <param name="tooltips">String array of tooltip values</param>
		public void SetTooltips(string[] tooltips)
		{
			this.tooltips = new string[PageCount];
			Array.Copy(tooltips, 0, this.tooltips, 0, tooltips.Length);
		}


		private string getTooltipText(int index)
		{
			if (tooltips == null)
				return "";
			else
				return Page.Server.HtmlEncode(tooltips[index]);
		}


		/// <summary>
		/// Clones the urrent instance of the control
		/// </summary>
		/// <returns>Clone of this control instance</returns>
		public object Clone()
		{
			SmartPager inst = new SmartPager();

			inst.display = display;
			inst.pageCount = pageCount;
			inst.currentPage = currentPage;
			inst.scriptPath = scriptPath;
			inst.mainTableStyle = mainTableStyle;
			inst.outputNextPrevLinks = outputNextPrevLinks;
			inst.navigatePreviousText = navigatePreviousText;
			inst.navigateNextText = navigateNextText;
			inst.outputFirstAndLastLinks = outputFirstAndLastLinks;
			inst.enableGoToPage = enableGoToPage;
			inst.fontSize = fontSize;
			inst.pageLinkForeColor = pageLinkForeColor;
			inst.pageLinkBackColor = pageLinkBackColor;
			inst.pageLinkHoverForeColor = pageLinkHoverForeColor;
			inst.pageLinkHoverBackColor = pageLinkHoverBackColor;
			inst.pageLinkSelectedForeColor = pageLinkSelectedForeColor;
			inst.pageLinkSelectedBackColor = pageLinkSelectedBackColor;
			inst.pageNumberBoxPadding = pageNumberBoxPadding;
			inst.pageNumberBoxBorderWidth = pageNumberBoxBorderWidth;
			inst.pageNumberBoxBorderColor = pageNumberBoxBorderColor;
			inst.ellipsisText = ellipsisText;
			inst.disabledNextPrevStyle = disabledNextPrevStyle;
			inst.tooltips = tooltips;

			return inst;
		}
	}
}